
/*
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++   
#
# 
# Copyright (c) 2015-2020, Kunlun BMC, ZD Technology (Beijing) Co., Ltd.. All   
# Rights Reserved.                                                               
# 
# You may not reproduce, distribute, publish, display, perform, modify, adapt,   
# transmit, broadcast, present, recite, release, license or otherwise exploit    
# any part of this publication in any form, by any means, without the prior      
# written permission of ZD Technology (Beijing) Co., Ltd..                       
#
#
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++   
*/

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <float.h>
#include <string.h>
#include <stdarg.h>
#include <limits.h>
#include <locale.h>
#include "disk-prediction.hpp"
#include <iostream>
#include <phosphor-logging/elog-errors.hpp>
#include "xyz/openbmc_project/Common/error.hpp"

using namespace phosphor::logging;
using namespace sdbusplus::xyz::openbmc_project::Logging::server;

constexpr const char* dbServiceBusName = "xyz.openbmc_project.data_center";
constexpr const char* dbPathBusName = "/xyz/openbmc_project/data_center";
constexpr const char* dbOperateIntfBusName = "xyz.openbmc_project.data_center.db_operate";

#define SVM_DEBUG 1
#define Malloc(type,n) (type *)malloc((n)*sizeof(type))
#define FSCANF(_stream, _format, _var) do{ if (fscanf(_stream, _format, _var) != 1) return false; }while(0)

template <class T> static inline void swap(T& x, T& y) { T t=x; x=y; y=t; }
template <class S, class T> static inline void clone(T*& dst, S* src, int n)
{
    dst = new T[n];
    memcpy((void *)dst,(void *)src,sizeof(T)*n);
}

bool read_model_header(FILE *fp, svm_model* model);
struct svm_model *svm_load_model(const char * fac);
void svm_free_and_destroy_model(svm_model** model_ptr_ptr);
int predict(svm_model* model, char *input);
void alarmReport(int predict_result, int slotId);
double svm_predict(const svm_model *model, const svm_node *x);
double svm_predict_values(const svm_model *model, const svm_node *x, double* dec_values);
static inline double powi(double base, int times);
int readDiskInfo(char *smartInfo, int *slotId);
int readDiskSmartInfo(char *smartInfo, int slotId);

int max_nr_attr = 64;

static const char *svm_type_table[] =
{
    "c_svc","nu_svc","one_class","epsilon_svr","nu_svr",NULL
};

static const char *kernel_type_table[]=
{
    "linear","polynomial","rbf","sigmoid","precomputed",NULL
};

double dot(const svm_node *px, const svm_node *py)
{
    double sum = 0;
    while(px->index != -1 && py->index != -1)
    {
        if(px->index == py->index)
        {
            sum += px->value * py->value;
            ++px;
            ++py;
        }
        else
        {
            if(px->index > py->index)
            {
                ++py;
            }
            else
            {
                ++px;
            }
        }
    }
    return sum;
}

double key_function(const svm_node *x, const svm_node *y,
			  const svm_parameter& param)
{
    switch(param.kernel_type)
    {
        case LINEAR:
            return dot(x,y);
        case POLY:
            return powi(param.gamma*dot(x,y)+param.coef0,param.degree);
        case RBF:
        {
            double sum = 0;
            while(x->index != -1 && y->index !=-1)
            {
                if(x->index == y->index)
                {
                    double d = x->value - y->value;
                    sum += d*d;
                    ++x;
                    ++y;
                }
                else
                {
                    if(x->index > y->index)
                    {
                        sum += y->value * y->value;
                        ++y;
                    }
                    else
                    {
                        sum += x->value * x->value;
                        ++x;
                    }
                }
            }

            while(x->index != -1)
            {
                sum += x->value * x->value;
                ++x;
            }

            while(y->index != -1)
            {
                sum += y->value * y->value;
                ++y;
            }

            return exp(-param.gamma*sum);
        }
        case SIGMOID:
            return tanh(param.gamma*dot(x,y)+param.coef0);
        case PRECOMPUTED:  //x: test (validation), y: SV
            return x[(int)(y->value)].value;
        default:
            return 0;  // Unreachable
    }
}

PredictionLoop::PredictionLoop(sdbusplus::bus::bus&& bus, const char* prefix, 
		const char* root, uint64_t interval) :
    _bus(std::move(bus)),
    _manager(_bus, root), _prefix(prefix), _root(root), _interval(interval),
    _event(sdeventplus::Event::get_default()),
    _timer(_event, std::bind(&PredictionLoop::read, this))
{
//    _interval = interval;
}

PredictionLoop::~PredictionLoop()
{
    svm_free_and_destroy_model(&_model);
}

void PredictionLoop::shutdown() noexcept
{
    //svm_free_and_destroy_model(&model);
    _event.exit(0);
}

void PredictionLoop::run()
{
    init();

    std::function<void()> callback(std::bind(&PredictionLoop::read, this));
    try
    {
        _timer.restart(std::chrono::microseconds(_interval));
        _bus.attach_event(_event.get(), SD_EVENT_PRIORITY_IMPORTANT);
        _event.loop();
    }
    catch (const std::exception& e)
    {
        log<level::ERR>("Error in sysfs polling loop",
                        entry("ERROR=%s", e.what()));
        throw;
    }
}

void PredictionLoop::init()
{
//load the disk prediction model.
    _model = svm_load_model("hd_failure_predict_model");
    if (_model == NULL)
    {
        fprintf(stderr, "[disk-predict] load model failed!\n");
    }
    return;
}

void PredictionLoop::read()
{
    //read disk SMART info, predict the disk state, and decides if report an alarm.
    int predict_result = 0;
    char input[128];
    char line[130];
    char *pStr = NULL;
    FILE *fp = NULL;
    int slotId = 0;
//    int ret = -1;

    fp = fopen("/etc/svm_models/disk_info.txt", "r");
    if(fp == NULL)
    {
        fprintf(stderr, "[disk-predict] open /etc/svm_models/disk_info.txt failed!\n");
        return;
    }
    while (fgets(line, 128, fp) != NULL)
    {
        pStr = strtok(line, " ");
        slotId = atoi(pStr);
        strcpy(input, "-1 ");
        strcat(input, line + strlen(pStr) + 1);
                
//        ret = readDiskInfo(input, &slotId);
//        if(ret != 0)
//        {
//            fprintf(stderr, "[disk-predict] read disk info failed! ret=%d\n", ret);
//            return;
//        }

        predict_result = predict(_model, input);
        if(predict_result == 1)
        {
            fprintf(stderr, "[disk-predict] SmartInfo: %s\n", input);
        }
        alarmReport(predict_result, slotId);  //raid: id 0 means raid1
    }
    fclose(fp);
    return;
}

static inline double powi(double base, int times)
{
    double tmp = base, ret = 1.0;

    for(int t=times; t>0; t/=2)
    {
        if(t%2==1)
        {
            ret*=tmp;
        }
        tmp = tmp * tmp;
    }
    return ret;
}

static char* readline(FILE *input, char**line, int* line_len)
{
    int len;

    if(fgets(*line, *line_len, input) == NULL)
    {
        return NULL;
    }

    while(strrchr(*line,'\n') == NULL)
    {
        *line_len *= 2;
        *line = (char *) realloc(*line, *line_len);
        len = (int) strlen(*line);
        if(fgets(*line + len, *line_len - len, input) == NULL)
        {
            break;
        }
    }
    return *line;
}

bool read_model_header(FILE *fp, svm_model* model)
{
    svm_parameter& param = model->param;
    // parameters for training only won't be assigned, but arrays are assigned as NULL for safety
    param.nr_weight = 0;
    param.weight_label = NULL;
    param.weight = NULL;

    char cmd[81];
    while(1)
    {
        FSCANF(fp,"%80s",cmd);

        if(strcmp(cmd,"svm_type")==0)
        {
            FSCANF(fp,"%80s",cmd);
            int i;
            for(i=0;svm_type_table[i];i++)
            {
                if(strcmp(svm_type_table[i],cmd)==0)
                {
                    param.svm_type=i;
                    break;
                }
            }
            
            if(svm_type_table[i] == NULL)
            {
                fprintf(stderr,"[disk-predict] unknown svm type.\n");
                return false;
            }
        }
        else if(strcmp(cmd,"kernel_type")==0)
        {
            FSCANF(fp,"%80s",cmd);
            int i;
            for(i=0;kernel_type_table[i];i++)
            {
                if(strcmp(kernel_type_table[i],cmd)==0)
                {
                    param.kernel_type=i;
                    break;
                }
            }
            if(kernel_type_table[i] == NULL)
            {
                fprintf(stderr,"[disk-predict] unknown kernel function.\n");
                return false;
            }
        }
        else if(strcmp(cmd,"degree")==0)
        {
            FSCANF(fp,"%d",&param.degree);
        }
        else if(strcmp(cmd,"gamma")==0)
        {
            FSCANF(fp,"%lf",&param.gamma);
        }
        else if(strcmp(cmd,"coef0")==0)
        {
            FSCANF(fp,"%lf",&param.coef0);
        }
        else if(strcmp(cmd,"nr_class")==0)
        {
            FSCANF(fp,"%d",&model->nr_class);
        }
        else if(strcmp(cmd,"total_sv")==0)
        {
            FSCANF(fp,"%d",&model->l);
        }
        else if(strcmp(cmd,"rho")==0)
        {
            int n = model->nr_class * (model->nr_class-1)/2;
            model->rho = Malloc(double,n);
            for(int i=0;i<n;i++)
            FSCANF(fp,"%lf",&model->rho[i]);
        }
        else if(strcmp(cmd,"label")==0)
        {
            int n = model->nr_class;
            model->label = Malloc(int,n);
            for(int i=0;i<n;i++)
            {
                FSCANF(fp,"%d",&model->label[i]);
            }
        }
        else if(strcmp(cmd,"probA")==0)
        {
            int n = model->nr_class * (model->nr_class-1)/2;
            model->probA = Malloc(double,n);
            for(int i=0;i<n;i++)
            {
                FSCANF(fp,"%lf",&model->probA[i]);
            }
        }
        else if(strcmp(cmd,"probB")==0)
        {
            int n = model->nr_class * (model->nr_class-1)/2;
            model->probB = Malloc(double,n);
            for(int i=0;i<n;i++)
            {
                FSCANF(fp,"%lf",&model->probB[i]);
            }
        }
        else if(strcmp(cmd,"prob_density_marks")==0)
        {
            int n = 10;	// nr_marks
            model->prob_density_marks = Malloc(double,n);
            for(int i=0;i<n;i++)
            {
                FSCANF(fp,"%lf",&model->prob_density_marks[i]);
            }
        }
        else if(strcmp(cmd,"nr_sv")==0)
        {
            int n = model->nr_class;
            model->nSV = Malloc(int,n);
            for(int i=0;i<n;i++)
            {
                FSCANF(fp,"%d",&model->nSV[i]);
            }
        }
        else if(strcmp(cmd,"SV")==0)
        {
            while(1)
            {
                int c = getc(fp);
                if(c==EOF || c=='\n') 
                {
                    break;
                }
            }
            break;
        }
        else
        {
            fprintf(stderr,"[disk-predict] unknown text in model file: [%s]\n",cmd);
            return false;
        }
    }

    return true;
}

struct svm_model *svm_load_model(const char * file)
{
    FILE *fp = NULL;
    char *old_locale = NULL;
    char *line;
    int line_len = 1024;
    char model_path[] = "/etc/svm_models/";
    char model_file_name[64] = {0};

    strcpy(model_file_name, model_path);
    strcat(model_file_name, file);
    
    fp = fopen(model_file_name,"rb");
    if(fp==NULL) 
    {
        fprintf(stderr, "[disk-predict] open file %s failed!\n", model_file_name);
        return NULL;
    }

    old_locale = setlocale(LC_ALL, NULL);
    if (old_locale) 
    {
        old_locale = strdup(old_locale);
    }
    setlocale(LC_ALL, "C");

    // read parameters

    struct svm_model *model = Malloc(struct svm_model,1);
    model->rho = NULL;
    model->probA = NULL;
    model->probB = NULL;
    model->prob_density_marks = NULL;
    model->sv_indices = NULL;
    model->label = NULL;
    model->nSV = NULL;

    // read header
    if (!read_model_header(fp, model))
    {
        fprintf(stderr, "[disk-predict] ERROR: fscanf failed to read model\n");
        setlocale(LC_ALL, old_locale);
        free(old_locale);
        free(model->rho);
        free(model->label);
        free(model->nSV);
        free(model);
        fclose(fp);
        return NULL;
    }

    // read sv_coef and SV
    int elements = 0;
    long pos = ftell(fp);

    line_len = 1024;
    line = Malloc(char, line_len);
    char *p,*endptr,*idx,*val;

    while(readline(fp, &line, &line_len)!=NULL)
    {
        p = strtok(line,":");
        while(1)
        {
            p = strtok(NULL,":");
            if(p == NULL)
            break;
            ++elements;
        }
    }
    elements += model->l;

    fseek(fp,pos,SEEK_SET);

    int m = model->nr_class - 1;
    int l = model->l;
    model->sv_coef = Malloc(double *,m);
    int i;
    for(i = 0; i < m; i++)
    {
        model->sv_coef[i] = Malloc(double,l);
    }
    
    model->SV = Malloc(struct svm_node*,l);
    svm_node *x_space = NULL;
    
    if(l > 0) 
    {
        x_space = Malloc(struct svm_node,elements);
    }

    int j=0;
    for(i=0;i<l;i++)
    {
        readline(fp, &line, &line_len);
        model->SV[i] = &x_space[j];

        p = strtok(line, " \t");
        model->sv_coef[0][i] = strtod(p,&endptr);
        for(int k=1;k<m;k++)
        {
            p = strtok(NULL, " \t");
            model->sv_coef[k][i] = strtod(p,&endptr);
        }

        while(1)
        {
            idx = strtok(NULL, ":");
            val = strtok(NULL, " \t");

            if(val == NULL)
            break;
            x_space[j].index = (int) strtol(idx,&endptr,10);
            x_space[j].value = strtod(val,&endptr);

            ++j;
        }
        x_space[j++].index = -1;
    }

    if(line != NULL)
    {
        free(line);
        line = NULL;
    }

    setlocale(LC_ALL, old_locale);
    free(old_locale);

    if (ferror(fp) != 0 || fclose(fp) != 0)
    {
        fprintf(stderr, "[disk-predict] ERROR: ferror/fclose failed!\n");
        setlocale(LC_ALL, old_locale);
        free(old_locale);
        free(model->rho);
        free(model->label);
        free(model->nSV);
        free(model);
        return NULL;
    }

    model->free_sv = 1;	// XXX
    return model;
}

void svm_free_model_content(svm_model* model_ptr)
{
    if(model_ptr->free_sv && model_ptr->l > 0 && model_ptr->SV != NULL)
    {
        free((void *)(model_ptr->SV[0]));
    }
    if(model_ptr->sv_coef)
    {
        for(int i=0;i<model_ptr->nr_class-1;i++)
        {
            free(model_ptr->sv_coef[i]);
        }
    }

    free(model_ptr->SV);
    model_ptr->SV = NULL;

    free(model_ptr->sv_coef);
    model_ptr->sv_coef = NULL;

    free(model_ptr->rho);
    model_ptr->rho = NULL;

    free(model_ptr->label);
    model_ptr->label = NULL;

    free(model_ptr->probA);
    model_ptr->probA = NULL;

    free(model_ptr->probB);
    model_ptr->probB = NULL;

    free(model_ptr->prob_density_marks);
    model_ptr->prob_density_marks = NULL;

    free(model_ptr->sv_indices);
    model_ptr->sv_indices = NULL;

    free(model_ptr->nSV);
    model_ptr->nSV = NULL;
}

void svm_free_and_destroy_model(svm_model** model_ptr_ptr)
{
    if(model_ptr_ptr != NULL && *model_ptr_ptr != NULL)
    {
        svm_free_model_content(*model_ptr_ptr);
        free(*model_ptr_ptr);
        *model_ptr_ptr = NULL;
    }
}

int predict(svm_model* model, char *input)
{
    int i = 0, ret = -1;
    double target_label, predict_label;
    char *idx, *val, *label, *endptr;
    int inst_max_index = -1; // strtol gives 0 if wrong format, and precomputed kernel has <index> start from 0
    char * line = Malloc(char, strlen(input) +1);
    struct svm_node* x = Malloc(struct svm_node, max_nr_attr);

    if(line == NULL  || x == NULL) // empty line
    {
        fprintf(stderr,"[disk-predict] %s, %d, malloc failed.\n", __func__, __LINE__);
        goto out_and_free;;
    }

    strcpy(line, input);
    label = strtok(line," \t\n");
    if(label == NULL) // empty line
    {
        fprintf(stderr,"[disk-predict] %s, %d, line is empty!\n", __func__, __LINE__);
        goto out_and_free;;
    }

    target_label = strtod(label,&endptr);
    if(endptr == label || *endptr != '\0')
    {
        fprintf(stderr,"[disk-predict] %s, %d, target label error!\n", __func__, __LINE__);
        goto out_and_free;;
    }

    while(1)
    {
        if(i>=max_nr_attr-1)	// need one more for index = -1
        {
            max_nr_attr *= 2;
            x = (struct svm_node *) realloc(x,max_nr_attr*sizeof(struct svm_node));
        }

        idx = strtok(NULL,":");
        val = strtok(NULL," \t");

        if(val == NULL)
        {
            break;
        }
        
        x[i].index = (int) strtol(idx, &endptr,10);
        
        if(endptr == idx || *endptr != '\0' || x[i].index <= inst_max_index)
        {
            fprintf(stderr,"[disk-predict] %s, %d, invalid SMART info!\n", __func__, __LINE__);
            goto out_and_free;;
        }
        else
        {
            inst_max_index = x[i].index;
        }
        
        x[i].value = strtod(val,&endptr);
        if(endptr == val || (*endptr != '\0' && !isspace(*endptr)))
        {
            fprintf(stderr,"[disk-predict] %s, %d, invalid SMART info!\n", __func__, __LINE__);
            goto out_and_free;
        }
        ++i;
    }
    
    x[i].index = -1;

    predict_label = svm_predict(model, x);

    if(predict_label == target_label)
    {
        ret = 0;
    }
    else
    {
        ret = 1;
    }

out_and_free:
    if(x != NULL)
    {
        free(x);
    }
    if(line != NULL)
    {
        free(line);
    }
    return ret;
}

int readDiskInfo(char *smartInfo, int *slotId)
{
    char line[128] = {0};

    strcpy(smartInfo, line);
    *slotId = 25;
    return 0;
}

using RiskReport = sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
using RiskReportArg = xyz::openbmc_project::Common::NotAllowed;

void alarmReport(int predict_result, int slotId)
{
    if(predict_result == 1)
    {
        //alarm, expect: negative, predict: positive).
        //The warning message need to refer IPMI interface specification.
        //Defined in meta-phosphor/recipes-phosphor/logging/files/sel_msg_config.json.
        std::string str = "Hard disk (slot" + std::to_string(slotId) + ") has risk of failure!";
//        if(report<RiskReport>(str) >=0)
        report<RiskReport>(RiskReportArg::REASON(str.c_str()));
        fprintf(stderr, "[disk-predict] successfully report. predict_result=%d (positve sample)\n", predict_result);
    }
    else if(predict_result == 0)
    {
        //disk is ok, equal to expect (negative).
//        fprintf(stderr, "[disk-predict] predict_result=%d (negative sample)\n", predict_result);
    }
    else
    {
        fprintf(stderr, "[disk-predict] failed to predict disk status!\n");
    }
}

double svm_predict(const svm_model *model, const svm_node *x)
{
    int nr_class = model->nr_class;
    double *dec_values;
    if(model->param.svm_type == ONE_CLASS ||
        model->param.svm_type == EPSILON_SVR ||
        model->param.svm_type == NU_SVR)
    {
        dec_values = Malloc(double, 1);
    }
    else
    {
        dec_values = Malloc(double, nr_class*(nr_class-1)/2);
    }
    double pred_result = svm_predict_values(model, x, dec_values);
    free(dec_values);
    return pred_result;
}

double svm_predict_values(const svm_model *model, const svm_node *x, double* dec_values)
{
    int i;
    if(model->param.svm_type == ONE_CLASS ||
        model->param.svm_type == EPSILON_SVR ||
        model->param.svm_type == NU_SVR)
    {
        double *sv_coef = model->sv_coef[0];
        double sum = 0;
        for(i=0;i<model->l;i++)
        {
            sum += sv_coef[i] * key_function(x,model->SV[i],model->param);
        }
        sum -= model->rho[0];
        *dec_values = sum;

        if(model->param.svm_type == ONE_CLASS)
        {
            return (sum>0)?1:-1;
        }
        else
        {
            return sum;
        }
    }
    else
    {
        int nr_class = model->nr_class;
        int l = model->l;

        double *kvalue = Malloc(double,l);
        for(i=0;i<l;i++)
        {
            kvalue[i] = key_function(x,model->SV[i],model->param);
        }

        int *start = Malloc(int,nr_class);
        start[0] = 0;
        for(i=1;i<nr_class;i++)
        {
            start[i] = start[i-1]+model->nSV[i-1];
        }

        int *vote = Malloc(int,nr_class);
        for(i=0;i<nr_class;i++)
        {
            vote[i] = 0;
        }

        int p=0;
        for(i=0;i<nr_class;i++)
        {
            for(int j=i+1;j<nr_class;j++)
            {
                double sum = 0;
                int si = start[i];
                int sj = start[j];
                int ci = model->nSV[i];
                int cj = model->nSV[j];

                int k;
                double *coef1 = model->sv_coef[j-1];
                double *coef2 = model->sv_coef[i];
                for(k=0;k<ci;k++)
                {
                    sum += coef1[si+k] * kvalue[si+k];
                }
                for(k=0;k<cj;k++)
                {
                    sum += coef2[sj+k] * kvalue[sj+k];
                }
                sum -= model->rho[p];
                dec_values[p] = sum;

                if(dec_values[p] > 0)
                {
                    ++vote[i];
                }
                else
                {
                    ++vote[j];
                }
                p++;
            }
        }

        int vote_max_idx = 0;
        for(i=1;i<nr_class;i++)
        {
            if(vote[i] > vote[vote_max_idx])
            {
                vote_max_idx = i;
            }
        }

        free(kvalue);
        free(start);
        free(vote);
        return model->label[vote_max_idx];
    }
}

